module;
#include <utility> // for declval()
#include <type_traits> // for true_type 

export module Code_20_5_1;


template<typename FROM, typename TO>
struct IsConvertibleT 
{
private:
    // to check whether we can call this helper func for a FROM object:
    static void func(TO);
    // test() trying to call the helper func():
    template<typename F, typename T, typename = decltype(func(std::declval<F>()))>
    static char test(void*);
    // test() fallback:
    template<typename, typename>
    static long test(...);
public:
    static constexpr bool value = sizeof(test<FROM, TO>(nullptr)) == 1;
};


template<typename FROM, typename TO>
constexpr bool IsConvertible = IsConvertibleT<FROM, TO>::value;



template<bool, typename T = void>
struct EnableIfT {};

template< typename T>
struct EnableIfT<true, T>
{
    using Type = T;
};

template<bool Cond, typename T = void>
using EnableIf = typename EnableIfT<Cond, T>::Type;

template<typename T1, typename T2>
class HasLess 
{
    template<typename T> struct Identity;

    template<typename U1, typename U2>
    static std::true_type test(Identity<decltype(std::declval<U1>() < std::declval<U2>())>*);

    template<typename U1, typename U2>
    static std::false_type  test(...);
public:
    static constexpr bool value = decltype(test<T1, T2>(nullptr))::value;
};


template<typename T1, typename T2, bool HasLess>
class LessResultImpl 
{
public:
    using Type = decltype(std::declval<T1>() < std::declval<T2>());
};

template<typename T1, typename T2>
class LessResultImpl<T1, T2, false> 
{
};

template<typename T1, typename T2>
class LessResultT : public LessResultImpl<T1, T2, HasLess<T1, T2>::value>
{
};

template<typename T1, typename T2>
using LessResult = typename LessResultT<T1, T2>::Type;


//[2]
template<typename T>
class IsContextualBoolT 
{
private:
    template<typename T> struct Identity;
    template<typename U>
    static std::true_type test(Identity<decltype(declval<U>() ? 0 : 1)>*);

    template<typename U>
    static std::false_type test(...);
public:
    static constexpr bool value = decltype(test<T>(nullptr))::value;
};
//[2]
template<typename T>
constexpr bool IsContextualBool = IsContextualBoolT<T>::value;

//[2]
template<typename T>
EnableIf<IsContextualBool<LessResult<T const&, T const&>>, T const&>min(T const& x, T const& y)
{
    if (y < x) {
        return y;
    }
    return x;
}

template<typename T>
EnableIf<IsConvertible<LessResult<T const&, T const&>, bool>, T const&> min(T const& x, T const& y)
{
    if (y < x) 
    {
        return y;
    }
    return x;
}

//template<typename T>
//T const& min(T const& x, T const& y)
//{
//    if (y < x) 
//    {
//        return y;
//    }
//    return x;
//}

struct X1 { };
bool operator< (X1 const&, X1 const&) { return true; }

struct X2 { };
bool operator<(X2, X2) { return true; }

struct X3 { };
bool operator<(X3&, X3&) { return true; }

struct X4 { };

struct BoolConvertible 
{
    operator bool() const { return true; } // implicit conversion to bool
};

struct X5 { };

BoolConvertible operator< (X5 const&, X5 const&)
{
    return BoolConvertible();
}

struct NotBoolConvertible // no conversion to bool
{ 
};

struct X6 { };

NotBoolConvertible operator< (X6 const&, X6 const&)
{
    return NotBoolConvertible();
}

struct BoolLike 
{
    explicit operator bool() const { return true; } // explicit conversion to bool
};

struct X7 { };
BoolLike operator< (X7 const&, X7 const&) { return BoolLike(); }

export
namespace Code_20_5_1 {

    void run()
    {
        min(X1(), X1()); // X1 can be passed to min()
        min(X2(), X2()); // X2 can be passed to min()
       //min(X3(), X3()); // ERROR: X3 cannot be passed to min()
       //min(X4(), X4()); // ERROR: X4 cannot be passed to min()
        min(X5(), X5()); // X5 can be passed to min()
       //min(X6(), X6()); // ERROR: X6 cannot be passed to min()
       //min(X7(), X7()); // UNEXPECTED ERROR: X7 cannot be passed to min()
    }
}